﻿using System;
using System.Collections.Generic;
using System.Collections;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using Microsoft.BizTalk.Streaming;
using Microsoft.Practices.ESB.Exception.Management;
using Microsoft.Practices.ESB.Resolver;
using Microsoft.Practices.ESB.Resolver.BRE;
using Microsoft.BizTalk.Message.Interop;
using Microsoft.BizTalk.Component.Interop;
//using Microsoft.BizTalk.XLANGs.BTXEngine;
using Microsoft.XLANGs.Core;
using Microsoft.XLANGs.BaseTypes;
using System.Xml;
using Microsoft.RuleEngine;

namespace BizTalk.ESB.Extensions.Resolvers
{
    public class BreContextResolver : IResolveProvider
    {
        public Dictionary<string, string> Resolve(ResolverInfo resolverInfo, XLANGMessage message)
        {
            Dictionary<String, String> resolverDictionary;
            Boolean useMessageContext = false;

            // Create instance of ContextResolution
            Resolution ctxResolution = new Resolution();

            // Create instance of BRE
            BRE breInfo = this.CreateResolverDescriptor(resolverInfo.Config, resolverInfo.Resolver, out useMessageContext);

            try
            {
                if (breInfo.useMsg && breInfo.recognizeMessageFormat)
                {
                    throw new ResolveException("Recognize Message Format not supported.");
                }

                ResolverMgr.SetContext(ctxResolution, message);
                XmlDocument msg = null;
                if (breInfo.useMsg)
                {
                    msg = (XmlDocument)message[0].RetrieveAs(typeof(XmlDocument));
                }

                ContextResolution msgCtx = null;
                if (useMessageContext)
                {
                    msgCtx = ContextResolverHelper.RetrieveContextProperties(message);
                }
                resolverDictionary = ResolveRules(resolverInfo.Config, resolverInfo.Resolver, ctxResolution, msg, breInfo, msgCtx);
                resolverDictionary = ContextResolverHelper.ResolveDictionary(message, resolverDictionary);
            }
            catch (Exception e)
            {
                EventLogger.Write(MethodBase.GetCurrentMethod(), e);
                throw;
            }
            finally
            {
                if (ctxResolution != null)
                {
                    ctxResolution = null;
                }
            }

            return resolverDictionary;
        }

        public Dictionary<string, string> Resolve(string config, string resolver, XmlDocument message)
        {
            Dictionary<String, String> resolverDictionary;
            Boolean useMessageContext = false;

            // Create instance of ContextResolution
            Resolution ctxResolution = new Resolution();

            // Create instance of BRE
            BRE breInfo = this.CreateResolverDescriptor(config, resolver, out useMessageContext);

            try
            {
                if (breInfo.useMsg && breInfo.recognizeMessageFormat)
                {
                    throw new ResolveException("Recognize Message Format not supported.");
                }
                resolverDictionary = ResolveRules(config, resolver, ctxResolution, message, breInfo, null);
            }
            catch (Exception e)
            {
                EventLogger.Write(MethodBase.GetCurrentMethod(), e);
                throw;
            }
            finally
            {
                if (ctxResolution != null)
                {
                    ctxResolution = null;
                }
            }

            return resolverDictionary;
        }

        public Dictionary<string, string> Resolve(string config, string resolver, IBaseMessage message, IPipelineContext pipelineContext)
        {
            Dictionary<String, String> resolverDictionary;
            Boolean useMessageContext = false;

            // Create instance of ContextResolution
            Resolution ctxResolution = new Resolution();

            // Create instance of BRE
            BRE breInfo = this.CreateResolverDescriptor(config, resolver, out useMessageContext);

            // Get Message
            XmlDocument msg = new XmlDocument();
            try
            {
                if (breInfo.useMsg && message.BodyPart != null)
                {
                    Stream originalDataStream = message.BodyPart.GetOriginalDataStream();
                    if (!originalDataStream.CanSeek)
                    {
                        ReadOnlySeekableStream s = new ReadOnlySeekableStream(originalDataStream);
                        message.BodyPart.Data = s;
                        pipelineContext.ResourceTracker.AddResource(s);
                        originalDataStream = s;
                    }

                    if (originalDataStream.Position != 0L)
                    {
                        originalDataStream.Position = 0L;
                    }

                    msg.Load(originalDataStream);
                    originalDataStream.Position = 0L;
                    ResolverMgr.SetContext(ctxResolution, message, pipelineContext);
                }
                ContextResolution msgCtx = null;
                if (useMessageContext)
                {
                    msgCtx = ContextResolverHelper.RetrieveContextProperties(message);
                }
                resolverDictionary = ResolveRules(config, resolver, ctxResolution, msg, breInfo, msgCtx);
                resolverDictionary = ContextResolverHelper.ResolveDictionary(message, resolverDictionary);
            }
            catch (Exception e)
            {
                EventLogger.Write(MethodBase.GetCurrentMethod(), e);
                throw;
            }
            finally
            {
                if (ctxResolution != null)
                {
                    ctxResolution = null;
                }
            }

            return resolverDictionary;
        }

        private static Dictionary<String, String> ResolveRules(String config, String resolver, Resolution resolution
            , XmlDocument message, BRE breInfo, ContextResolution messageContext)
        {
            // DEBUG
            System.Diagnostics.Trace.WriteLine("CONFIG = " + config);
            System.Diagnostics.Trace.WriteLine("RESOLVER = " + resolver);

            Dictionary<String, String> resolverInfo = new Dictionary<String, String>();
            Dictionary<String, String> resolverDictionary = new Dictionary<String, String>();
            Dictionary<String, String> facts = null;

            Int16 PolicyMajorVersion = 0;
            Int16 PolicyMinorVersion = 0;
            String[] VersionArray = null;
            Object[] policyFacts = null;
            Policy brePolicy = null;
            TypedXmlDocument xmlMsgForBre = null;
            String DocType = "Microsoft.Practices.ESB.ResolveProviderMessage";

            if (!resolver.Contains(@":\"))
            {
                resolver = resolver + @":\";
            }
            try
            {
                EventLogger.Write("Resolution strong name is {0}", new object[] { resolution.DocumentSpecStrongNameField });

                // Set the Doc Type
                if (String.IsNullOrEmpty(resolution.DocumentSpecStrongNameField) == false && breInfo.recognizeMessageFormat)
                {
                    Int32 index = resolution.DocumentSpecStrongNameField.IndexOf(",", StringComparison.CurrentCultureIgnoreCase);
                    if ((index > 0) && (index < resolution.DocumentSpecStrongNameField.Length))
                    {
                        DocType = resolution.DocumentSpecStrongNameField.Substring(0, index);
                    }
                    else
                    {
                        DocType = resolution.DocumentSpecStrongNameField;
                    }
                }
                EventLogger.Write("DocType for typed xml document is {0}", new object[] { DocType });

                if (String.IsNullOrEmpty(breInfo.version) == false)
                {
                    VersionArray = breInfo.version.Split(".".ToCharArray());
                    if (VersionArray != null)
                    {
                        PolicyMajorVersion = Convert.ToInt16(VersionArray[0], System.Globalization.NumberFormatInfo.CurrentInfo);
                        PolicyMinorVersion = Convert.ToInt16(VersionArray[1], System.Globalization.NumberFormatInfo.CurrentInfo);
                    }
                }

                if (breInfo.useMsg)
                {
                    if (messageContext != null)
                    {
                        policyFacts = new Object[3];
                        xmlMsgForBre = new TypedXmlDocument(DocType, message);
                        policyFacts[0] = resolution;
                        policyFacts[1] = xmlMsgForBre;
                        policyFacts[2] = messageContext;
                    }
                    else
                    {
                        policyFacts = new Object[2];
                        xmlMsgForBre = new TypedXmlDocument(DocType, message);
                        policyFacts[0] = resolution;
                        policyFacts[1] = xmlMsgForBre;
                    }
                }
                else
                {
                    if (messageContext != null)
                    {
                        policyFacts = new Object[2];
                        policyFacts[0] = resolution;
                        policyFacts[1] = messageContext;
                    }
                    else
                    {
                        policyFacts = new Object[] { resolution };
                    }
                }

                if (PolicyMajorVersion > 0)
                {
                    brePolicy = new Policy(breInfo.policy, PolicyMajorVersion, PolicyMinorVersion);
                }
                else
                {
                    brePolicy = new Policy(breInfo.policy);
                }

                brePolicy.Execute(policyFacts);
                brePolicy.Dispose();
                if (policyFacts[0] == null)
                {
                    throw new ResolveException("Error executing BRE Resolver");
                }

                resolution = (Resolution)policyFacts[0];
                ResolverMgr.SetResolverDictionary(resolution, resolverDictionary);
                resolverInfo = resolverDictionary;
            }
            catch (Exception e)
            {
                EventLogger.Write(MethodBase.GetCurrentMethod(), e);
                throw;
            }
            finally
            {
                if (facts != null)
                {
                    facts.Clear();
                    facts = null;
                }
                if (resolution != null)
                {
                    resolution = null;
                }
                if (resolverDictionary != null)
                {
                    resolverDictionary = null;
                }
            }

            return resolverInfo;
        }

        private BRE CreateResolverDescriptor(String config, String resolver, out Boolean UseMsgContext)
        {
            BRE breWrapper;
            UseMsgContext = false;

            if (config == null)
            {
                throw new ArgumentNullException("config");
            }
            if (resolver == null)
            {
                throw new ArgumentNullException("resolver");
            }

            Dictionary<String, String> facts = null;
            try
            {
                BRE breTemp = new BRE();
                breTemp.useMsg = false;
                facts = ResolverMgr.GetFacts(config, resolver);
                breTemp.policy = ResolverMgr.GetConfigValue(facts, true, "policy");
                breTemp.version = ResolverMgr.GetConfigValue(facts, false, "version");
                Boolean.TryParse(ResolverMgr.GetConfigValue(facts, false, "useMsgContext"), out UseMsgContext); ;
                Boolean useMessage, recognizeMessage;
                breTemp.useMsgSpecified = Boolean.TryParse(
                    ResolverMgr.GetConfigValue(facts, false, "useMsg")
                    , out useMessage);
                breTemp.recognizeMessageFormatSpecified = Boolean.TryParse(
                    ResolverMgr.GetConfigValue(facts, false, "recognizeMessageFormat")
                    , out recognizeMessage);
                breTemp.useMsg = useMessage;
                breTemp.recognizeMessageFormat = recognizeMessage;

                breWrapper = breTemp;
            }
            catch (Exception e)
            {
                EventLogger.Write(MethodBase.GetCurrentMethod(), e);
                throw;
            }
            finally
            {
                if (facts != null)
                {
                    facts.Clear();
                    facts = null;
                }
            }

            return breWrapper;
        }

    }

    public class StaticContextResolver : IResolveProvider
    {
        public Dictionary<string, string> Resolve(ResolverInfo resolverInfo, XLANGMessage message)
        {
            // Create instance of ContextResolution
            Resolution ctxResolution = new Resolution();
            ResolverMgr.SetContext(ctxResolution, message);

            // Call ResolveStatic
            Dictionary<String, String> dictionary = ResolveStatic(resolverInfo.Config, resolverInfo.Resolver, ctxResolution);
            dictionary = ContextResolverHelper.ResolveDictionary(message, dictionary);
            return dictionary;
        }

        public Dictionary<string, string> Resolve(string config, string resolver, XmlDocument message)
        {
            // Create instance of ContextResolution
            Resolution ctxResolution = new Resolution();

            // Call ResolveStatic
            return ResolveStatic(config, resolver, ctxResolution);
        }

        public Dictionary<string, string> Resolve(string config, string resolver, IBaseMessage message, IPipelineContext pipelineContext)
        {
            // Create instance of ContextResolution
            Resolution ctxResolution = new Resolution();
            ResolverMgr.SetContext(ctxResolution, message, pipelineContext);

            // Call ResolveStatic
            Dictionary<String, String> dictionary = ResolveStatic(config, resolver, ctxResolution);
            dictionary = ContextResolverHelper.ResolveDictionary(message, dictionary);
            return dictionary;
        }

        private static Dictionary<String, String> ResolveStatic(String config, String resolver, Resolution resolution)
        {
            // DEBUG
            System.Diagnostics.Trace.WriteLine("CONFIG = " + config);
            System.Diagnostics.Trace.WriteLine("RESOLVER = " + resolver);

            Dictionary<String, String> resolverInfo = new Dictionary<String, String>();
            Dictionary<String, String> resolverDictionary = new Dictionary<String, String>();
            Dictionary<String, String> facts = null;

            // Add keys from config
            // Add keys from resolver string
            // Add keys from resolution
            if (!resolver.Contains(@":\"))
            {
                resolver = resolver + @":\";
            }
            try
            {
                facts = ResolverMgr.GetFacts(config, resolver);
                resolution.ActionField = ResolverMgr.GetConfigValue(facts, true, "action");
                resolution.MessageExchangePattern = ResolverMgr.GetConfigValue(facts, false, "messageExchangePattern");
                resolution.EndpointConfig = ResolverMgr.GetConfigValue(facts, false, "endpointConfig");
                Boolean JaxRpc;
                Boolean.TryParse(ResolverMgr.GetConfigValue(facts, false, "fixJaxRpc"), out JaxRpc);
                resolution.FixJaxRpc = JaxRpc;
                resolution.TargetNamespace = ResolverMgr.GetConfigValue(facts, false, "targetNamespace");
                resolution.TransformType = ResolverMgr.GetConfigValue(facts, false, "transformType");
                resolution.TransportLocation = ResolverMgr.GetConfigValue(facts, false, "transportLocation");
                resolution.TransportType = ResolverMgr.GetConfigValue(facts, false, "transportType");

                ResolverMgr.SetResolverDictionary(resolution, resolverDictionary);
                resolverInfo = resolverDictionary;
            }
            catch (Exception e)
            {
                EventLogger.Write(MethodBase.GetCurrentMethod(), e);
                throw;
            }
            finally
            {
                if (facts != null)
                {
                    facts.Clear();
                    facts = null;
                }
                if (resolution != null)
                {
                    resolution = null;
                }
                if (resolverDictionary != null)
                {
                    resolverDictionary = null;
                }
            }

            return resolverInfo;
        }
    }

    public class ContextResolverHelper
    {
        public static Dictionary<String, String> ResolveDictionary(XLANGMessage message, Dictionary<String, String> dictionary)
        {
            Dictionary<String, String> resolvedDictionary = new Dictionary<String, String>();
            foreach (KeyValuePair<String, String> item in dictionary)
            {
                resolvedDictionary.Add(item.Key, ResolveContext(item.Value, RetrieveContextProperties(message)));
            }
            if ((!String.IsNullOrEmpty(resolvedDictionary["Resolver.ActionField"]))
                && String.IsNullOrEmpty(resolvedDictionary["Resolver.Action"]))
            {
                resolvedDictionary["Resolver.Action"] = resolvedDictionary["Resolver.ActionField"];
            }
            if (!string.IsNullOrEmpty(resolvedDictionary["Resolver.EndpointConfig"]))
            {
                var endpointconfigparts = resolvedDictionary["Resolver.EndpointConfig"].Split("&".ToCharArray(), StringSplitOptions.RemoveEmptyEntries);
                foreach (var item in endpointconfigparts)
                {
                    var key = item.Substring(0, item.IndexOf("="));
                    var value = item.Substring(item.IndexOf("=") + 1);
                    resolvedDictionary.Add("Resolver." + key, value.TrimEnd(";".ToCharArray()));
                    System.Diagnostics.Trace.WriteLine("Added Resolver Entry: key = Resolver." + key + ", value = " + value.TrimEnd(";".ToCharArray()));
                }
            }
            return resolvedDictionary;
        }

        public static Dictionary<String, String> ResolveDictionary(IBaseMessage message, Dictionary<String, String> dictionary)
        {
            Dictionary<String, String> resolvedDictionary = new Dictionary<String, String>();
            foreach (KeyValuePair<String, String> item in dictionary)
            {
                resolvedDictionary.Add(item.Key, ResolveContext(item.Value, message.Context));
            }
            if ((!String.IsNullOrEmpty(resolvedDictionary["Resolver.ActionField"]))
                && String.IsNullOrEmpty(resolvedDictionary["Resolver.Action"]))
            {
                resolvedDictionary["Resolver.Action"] = resolvedDictionary["Resolver.ActionField"];
            }
            return resolvedDictionary;
        }

        private static string ResolveContext(string keyWithContext, IBaseMessageContext context)
        {
            var separator = "%";
            var moniker = @"CONTEXT:\\";

            var keyParts = keyWithContext.Split(separator.ToCharArray(), StringSplitOptions.RemoveEmptyEntries);
            var sb = new System.Text.StringBuilder();

            foreach (string part in keyParts)
            {
                if (part.StartsWith(moniker))
                {
                    string contextValue = ExtractContextValue(part, moniker, separator, context);
                    sb.Append(contextValue);
                }
                else
                {
                    sb.Append(part);
                }
            }
            return sb.ToString();
        }

        private static string ExtractContextValue(string part, string moniker, string separator, IBaseMessageContext context)
        {
            string propName = string.Empty;
            string propNamespace = string.Empty;
            string propDefault = string.Empty;
            string returnValue = part;

            if (ParseContextString(part, moniker, separator, out propName, out propNamespace, out propDefault))
            {
                // Get value from property
                string propValue = Convert.ToString(context.Read(propName, propNamespace));
                if (String.IsNullOrEmpty(propValue))
                {
                    returnValue = propDefault;
                }
                else
                {
                    returnValue = propValue;
                }
            }

            return returnValue;
        }

        private static string ResolveContext(string keyWithContext, ContextResolution messageContext)
        {
            // Replace %Context:\\....% with the context value
            var separator = "%";
            var moniker = @"CONTEXT:\\";

            var keyParts = keyWithContext.Split(separator.ToCharArray(), StringSplitOptions.RemoveEmptyEntries);
            var sb = new System.Text.StringBuilder();

            foreach (string part in keyParts)
            {
                if (part.StartsWith(moniker))
                {
                    string contextValue = ExtractContextValue(part, moniker, separator, messageContext);
                    sb.Append(contextValue);
                }
                else
                {
                    sb.Append(part);
                }
            }
            return sb.ToString();
        }

        private static string ExtractContextValue(string part, string moniker, string separator, ContextResolution messageContext)
        {
            string propName = string.Empty;
            string propNamespace = string.Empty;
            string propDefault = string.Empty;
            string returnValue = part;

            if (ParseContextString(part, moniker, separator, out propName, out propNamespace, out propDefault))
            {
                // Get value from property
                var propValue = messageContext.GetContextPropertyValue(propName, propNamespace);
                if (String.IsNullOrEmpty(propValue))
                {
                    returnValue = propDefault;
                }
                else
                {
                    returnValue = propValue;
                }
            }

            return returnValue;
        }

        private static Boolean ParseContextString(String contextInfo, string moniker, string separator, out String PropertyName, out String PropertyNamespace, out String DefaultValue)
        {
            PropertyName = String.Empty;
            PropertyNamespace = String.Empty;
            DefaultValue = String.Empty;
            Boolean result = false;

            if (!String.IsNullOrEmpty(contextInfo))
            {
                if (contextInfo.StartsWith(moniker))
                {
                    result = true;
                    String[] parts = contextInfo.TrimStart(moniker.ToCharArray()).TrimEnd(separator.ToCharArray())
                        .Split(",".ToCharArray(), StringSplitOptions.RemoveEmptyEntries);
                    foreach (String s in parts)
                    {
                        String[] fieldParts = s.Split("=".ToCharArray(), StringSplitOptions.RemoveEmptyEntries);
                        if (fieldParts.Length != 2)
                        {
                            throw new ArgumentException(@"Invalid Context info syntax.  Should be '%CONTEXT:\\name=ReceivePortName,namespace=http://schemas.microsoft.com/BizTalk/2003/system-properties,default=NA%'");
                        }
                        else
                        {
                            String name = fieldParts[0];
                            String val = fieldParts[1];
                            switch (name.ToUpper())
                            {
                                case "NAME":
                                    {
                                        PropertyName = val;
                                        break;
                                    }
                                case "NAMESPACE":
                                    {
                                        PropertyNamespace = val;
                                        break;
                                    }
                                case "DEFAULT":
                                    {
                                        DefaultValue = val;
                                        break;
                                    }
                            }
                        }
                    }
                }
            }
            return result;
        }

        internal static ContextResolution RetrieveContextProperties(XLANGMessage message)
        {
            if (message != null)
            {
                ContextResolution msgCtx = new ContextResolution();
                System.Collections.Hashtable props = GetXlangMessageContext(message);
                msgCtx.Properties = new List<ContextProperty>(props.Count);

                if (props != null)
                {
                    foreach (System.Collections.DictionaryEntry de in props)
                    {
                        ContextProperty c = new ContextProperty();
                        c.PropertyValue = Convert.ToString(de.Value);
                        c.PropertyName = Convert.ToString(((XmlQName)de.Key).Name);
                        c.PropertyNamespace = Convert.ToString(((XmlQName)de.Key).Namespace);
                        msgCtx.Properties.Add(c);
                    }
                }
                return msgCtx;
            }
            else
            {
                return null;
            }
        }

        internal static ContextResolution RetrieveContextProperties(IBaseMessage message)
        {
            ContextResolution msgCtx = new ContextResolution();
            msgCtx.Properties = new List<ContextProperty>(Convert.ToInt32(message.Context.CountProperties));
            for (Int32 i = 0; i < message.Context.CountProperties; i++)
            {
                String propName = String.Empty;
                String propNamespace = String.Empty;
                Object propValue = message.Context.ReadAt(i, out propName, out propNamespace);
                ContextProperty c = new ContextProperty();
                c.PropertyValue = Convert.ToString(propValue);
                c.PropertyName = propName;
                c.PropertyNamespace = propNamespace;
                msgCtx.Properties.Add(c);
            }
            return msgCtx;
        }

        private static Hashtable GetXlangMessageContext(XLANGMessage message)
        {
            try
            {
                foreach (Segment segment in Service.RootService._segments)
                {
                    IDictionary fields =
                        Context.FindFields(
                          typeof(XLANGMessage)
                        , segment.ExceptionContext);

                    foreach (DictionaryEntry field in fields)
                    {
                        XMessage msg = (field.Value as XMessage);
                        if (msg == null)
                            continue;

                        if (String.Compare(msg.Name, message.Name) != 0)
                        {
                            continue;
                        }
                        else
                        {
                            return msg.GetContextProperties();
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                EventLogger.Write("Failure retrieving context for XlangMessage: " + ex.Message);
            }
            return null;
        }
    }
}
